/*
 * TextComponent.java  0.4.0 / Oct 27, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import com.cute4j.swing.events.EditListener;
import com.cute4j.swing.events.UndoRedoListener;
import com.cute4j.swing.print.Printer;
import com.cute4j.swing.properties.TextComponentProperties;
import com.cute4j.swing.text.TextDocument;

/**
 * Constructs new TextComponent object.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public class TextComponent extends Component
                           implements TextComponentProperties {

// Static Initialization. ------------------------------------------------------

  static {
    _initialize();
  }

// Package-Private Fields. -----------------------------------------------------

  TextDocument document;
  boolean documentOwner;
  boolean editable;
  boolean undoAvailable;
  boolean redoAvailable;
  boolean cutAvailable;
  boolean copyAvailable;
  boolean pasteAvailable;
  boolean clearAvailable;
  boolean selectAllAvailable;

// Package-Private Constructors. -----------------------------------------------

  TextComponent(TextDocument document) {
    editable = true;
    this.document = document;
    _setDocument(_delegate, document); // NOPMD
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Clears text.
   */
  public void clearText() {
    if (document != null) {
      document.clearText();
    }
  }

  /**
   * Returns text.
   *
   * @return text
   */
  public String getText() {
    if (document != null) {
      return document.getText();
    }
    return null;
  }

  /**
   * Sets text.
   *
   * @param text the text
   */
  public void setText(String text) {
    if (document != null) {
      document.setText(text);
    }
  }

  /**
   * Appends text.
   *
   * @param text the text
   */
  public void appendText(String text) {
    if (document != null) {
      document.appendText(text);
    }
  }

  /**
   * Inserts text at current cursor position.
   *
   * @param text the text to be inserted
   */
  public void insertText(String text) {
    if ((document != null) && (text != null) && isEnabled() && isEditable()) {
      disposedCheck();
      _insertText(_delegate, text);
    }
  }

  /**
   * Returns <tt>true</tt> if the text component is editable.
   *
   * @return <tt>true</tt> if the text component is editable
   */
  public boolean isEditable() {
    return editable;
  }

  /**
   * Sets whether or not the text component is editable.
   *
   * @param editable true if editable
   */
  public void setEditable(boolean editable) {
    if ((document != null) && isEnabled()) {
      final boolean oldEditable = this.editable;
      if (oldEditable != editable) {
        disposedCheck();
        _setEditable(_delegate, editable);
        this.editable = editable;
        firePropertyChange(PROPERTY_EDITABLE, oldEditable, editable);
      }
    }
  }

  /**
   * Returns <tt>true</tt> if the undo operation is available.
   *
   * @return <tt>true</tt> if the undo operation is available
   */
  public boolean isUndoAvailable() {
    return undoAvailable;
  }

  /**
   * Returns <tt>true</tt> if the redo operation is available.
   *
   * @return <tt>true</tt> if the redo operation is available
   */
  public boolean isRedoAvailable() {
    return redoAvailable;
  }

  /**
   * Returns <tt>true</tt> if the cut operation is available.
   *
   * @return <tt>true</tt> if the cut operation is available
   */
  public boolean isCutAvailable() {
    return cutAvailable;
  }

  /**
   * Returns <tt>true</tt> if the copy operation is available.
   *
   * @return <tt>true</tt> if the copy operation is available
   */
  public boolean isCopyAvailable() {
    return copyAvailable;
  }

  /**
   * Returns <tt>true</tt> if the paste operation is available.
   *
   * @return <tt>true</tt> if the paste operation is available
   */
  public boolean isPasteAvailable() {
    return pasteAvailable;
  }

  /**
   * Returns <tt>true</tt> if the clear operation is available.
   *
   * @return <tt>true</tt> if the clear operation is available
   */
  public boolean isClearAvailable() {
    return clearAvailable;
  }

  /**
   * Returns <tt>true</tt> if the select all operation is available.
   *
   * @return <tt>true</tt> if the select all operation is available
   */
  public boolean isSelectAllAvailable() {
    return selectAllAvailable;
  }

  /**
   * Undos last action.
   */
  public void undo() {
    if (undoAvailable) {
      document.undo();
      requestFocus();
    }
  }

  /**
   * Redos last action.
   */
  public void redo() {
    if (redoAvailable) {
      document.redo();
      requestFocus();
    }
  }

  /**
   * Cuts selected text.
   */
  public void cut() {
    if (cutAvailable) {
      disposedCheck();
      _cut(_delegate);
      requestFocus();
    }
  }

  /**
   * Copies selected text.
   */
  public void copy() {
    if (copyAvailable) {
      disposedCheck();
      _copy(_delegate);
      if (editable) {
        requestFocus();
      }
    }
  }

  /**
   * Pastes text from the clipboard.
   */
  public void paste() {
    if (pasteAvailable) {
      disposedCheck();
      _paste(_delegate);
      requestFocus();
    }
  }

  /**
   * Clears selected text.
   */
  public void clear() {
    if (clearAvailable) {
      disposedCheck();
      _clear(_delegate);
      requestFocus();
    }
  }

  /**
   * Selects all text.
   */
  public void selectAll() {
    if (selectAllAvailable) {
      disposedCheck();
      _selectAll(_delegate);
      requestFocus();
    }
  }

  /**
   * Returns <tt>true</tt> if the content of the text area is modified.
   *
   * @return <tt>true</tt> if the content of the text area is modified
   */
  public boolean isModified() {
    if (document != null) {
      return document.isModified();
    }
    return false;
  }

  /**
   * Sets whether or not content of the text area is modified.
   *
   * @param modified <tt>true</tt> if modified.
   */
  public void setModified(boolean modified) {
    if (document != null) {
      document.setModified(modified);
    }
  }

  /**
   * Reads text from reader.
   *
   * @param reader the reader
   *
   * @throws IOException on an I/O error
   */
  public void read(Reader reader) throws IOException {
    if (document != null) {
      document.read(reader);
    }
  }

  /**
   * Writes text to writer.
   *
   * @param writer the writer
   *
   * @throws IOException on an I/O error
   */
  public void write(Writer writer) throws IOException {
    if (document != null) {
      document.write(writer);
    }
  }

  /**
   * Prints content of the text area.
   *
   * @param printer the printer
   */
  public void print(Printer printer) {
    if (document != null) {
      document.print(printer);
    }
  }

  /**
   * Adds undo/redo listener.
   *
   * @param listener the undo/redo listener
   */
  public void addUndoRedoListener(UndoRedoListener listener) {
    if (listener != null) {
      listenerList.add(UndoRedoListener.class, listener);
    }
  }

  /**
   * Remove undo/redo listener.
   *
   * @param listener the undo/redo listener
   */
  public void removeUndoRedoListener(UndoRedoListener listener) {
    if (listener != null) {
      listenerList.remove(UndoRedoListener.class, listener);
    }
  }

  /**
   * Returns all undo/redo listeners.
   *
   * @return the undo/redo listener array
   */
  public UndoRedoListener[] getUndoRedoListeners() {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    UndoRedoListener[] listeners = new UndoRedoListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(UndoRedoListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

  /**
   * Adds edit listener.
   *
   * @param listener the edit listener
   */
  public void addEditListener(EditListener listener) {
    if (listener != null) {
      listenerList.add(EditListener.class, listener);
    }
  }

  /**
   * Remove edit listener.
   *
   * @param listener the edit listener
   */
  public void removeEditListener(EditListener listener) {
    if (listener != null) {
      listenerList.remove(EditListener.class, listener);
    }
  }

  /**
   * Returns all edit listeners.
   *
   * @return the edit listener array
   */
  public EditListener[] getEditListeners() {
    int count = listenerList.getListenerCount(EditListener.class);
    EditListener[] listeners = new EditListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(EditListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Notifies listeners that undo operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireUndoAvailable(boolean available) {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    if (count > 0) {
      UndoRedoListener[] listeners =
          (UndoRedoListener[])listenerList.
                                      getListenersArray(UndoRedoListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].undoAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that redo operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireRedoAvailable(boolean available) {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    if (count > 0) {
      UndoRedoListener[] listeners =
          (UndoRedoListener[])listenerList.
                                      getListenersArray(UndoRedoListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].redoAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that cut operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireCutAvailable(boolean available) {
    int count = listenerList.getListenerCount(EditListener.class);
    if (count > 0) {
      EditListener[] listeners =
            (EditListener[])listenerList.getListenersArray(EditListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].cutAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that copy operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireCopyAvailable(boolean available) {
    int count = listenerList.getListenerCount(EditListener.class);
    if (count > 0) {
      EditListener[] listeners =
            (EditListener[])listenerList.getListenersArray(EditListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].copyAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that paste operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void firePasteAvailable(boolean available) {
    int count = listenerList.getListenerCount(EditListener.class);
    if (count > 0) {
      EditListener[] listeners =
            (EditListener[])listenerList.getListenersArray(EditListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].pasteAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that clear operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireClearAvailable(boolean available) {
    int count = listenerList.getListenerCount(EditListener.class);
    if (count > 0) {
      EditListener[] listeners =
            (EditListener[])listenerList.getListenersArray(EditListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].clearAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that clear operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireSelectAllAvailable(boolean available) {
    int count = listenerList.getListenerCount(EditListener.class);
    if (count > 0) {
      EditListener[] listeners =
            (EditListener[])listenerList.getListenersArray(EditListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].selectAllAvailable(available);
      }
    }
  }

// Overridden Methods. ---------------------------------------------------------

  void componentDestroyed() {
    super.componentDestroyed();
    if (documentOwner && !document.isDisposed()) {
      document.dispose();
    }
  }

// Package-Private Methods. ----------------------------------------------------

  void setDocument(TextDocument document) {
    final TextDocument oldDocument = this.document;
    if (oldDocument == null) {
      if (fixedSize == null) {
        return;
      }
    } else {
      if (oldDocument.equals(document)) {
        return;
      }
    }
    if ((oldDocument != null) && documentOwner) {
      oldDocument.dispose();
    }
    disposedCheck();
    if (document == null) {
      _setDocument(_delegate, null);
      setEditable(false);
    } else {
      _setDocument(_delegate, document);
      if (editable) {
        _setEditable(_delegate, true);
      }
    }
    this.document = document;
    documentOwner = false;
    invalidate();
    firePropertyChange(PROPERTY_DOCUMENT, oldDocument, document);
  }

// Native Callbacks. -------------------------------------------------------------

  void _undoAvailable(boolean available)  {
    undoAvailable = available;
    fireUndoAvailable(available);
  }

  void _redoAvailable(boolean available)  {
    redoAvailable = available;
    fireRedoAvailable(available);
  }

  void _cutAvailable(boolean available) {
    cutAvailable = available;
    fireCutAvailable(available);
  }

  void _copyAvailable(boolean available) {
    copyAvailable = available;
    fireCopyAvailable(available);
  }

  void _pasteAvailable(boolean available) {
    pasteAvailable = available;
    firePasteAvailable(available);
  }

  void _clearAvailable(boolean available) {
    clearAvailable = available;
    fireClearAvailable(available);
  }

  void _selectAllAvailable(boolean available) {
    selectAllAvailable = available;
    fireSelectAllAvailable(available);
  }

// Package-Private Native Methods. ---------------------------------------------

  native void _setDocument(long delegate, TextDocument document);

  native void _setEditable(long delegate, boolean editable);

  native void _insertText(long delegate, String text);

  native void _cut(long delegate);

  native void _copy(long delegate);

  native void _paste(long delegate);

  native void _clear(long delegate);

  native void _selectAll(long delegate);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize();

}
